package com.calculator;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

/**
 * 计算器类（Calculator）功能
 * 1.可以实现两个数的加、减、乘、除运算
 * 2.可以进行undo和redo操作，即撤销和重做的操作
 * undo：撤销最近一次操作。
 * redo：重做最近一次撤销的操作。
 * 3.可以进行clear操作，即清空操作
 * clear 清空上下文所有操作。
 * 4.输入的运算数为小数时需要保证精度10(可自行设置)
 * 5.输入的运算数为整数时加减乘避免使用BigDecimal内置运算（成本大），其中乘2的幂次方时使用位运算进一步提升运算效率
 */
public class Calculator {
    //除法精度(可自行设置)
    private int divideScale = 10;
    //退出
    private static String quit = "quit";
    //支持的操作
    private static Set<String> operationSet = new HashSet<>(Arrays.asList("+", "-", "*", "/", "undo", "redo", "clear", "quit"));
    //基本运算外的操作
    private static Set<String> specialOperationSet = new HashSet<>(Arrays.asList("undo", "redo", "clear"));
    //当前计算结果
    private static BigDecimal result = BigDecimal.ZERO;
    //操作历史记录栈
    private static Stack<OperationRecord> history = new Stack<>();
    //重做历史记录栈
    private Stack<OperationRecord> redoHistory = new Stack<>();

    //记录每次操作的信息
    private static class OperationRecord {
        //操作类型
        String operation;
        //操作前的结果
        BigDecimal before;
        //操作后的结果
        BigDecimal after;
        //操作数
        BigDecimal operand;

        public OperationRecord(String op, BigDecimal b, BigDecimal a, BigDecimal o) {
            this.operation = op;
            this.before = b;
            this.after = a;
            this.operand = o;
        }
    }

    /**
     * 包含小数运算
     * @param operation
     * @param num
     */
    private void performDecimalOperation(String operation, BigDecimal num) {
        switch (operation) {
            case "+":
                result = result.add(num);
                break;
            case "-":
                result = result.subtract(num);
                break;
            case "*":
                result = result.multiply(num);
                break;
            case "/":
                result = result.divide(num, divideScale, RoundingMode.HALF_UP);
                break;
        }
    }

    /**
     * 整数运算（2的幂乘法优化）
     * @param operation
     * @param num
     */
    private void performIntegerOperation(String operation, long num) {
        switch (operation) {
            case "+":
                result = new BigDecimal(result.longValue() + num);
                break;
            case "-":
                result = new BigDecimal(result.longValue() - num);
                break;
            case "*":
                //乘2的幂次方时使用位运算进一步提升运算效率
                result = new BigDecimal(isPowerOfTwo(num) ? multiplyIfPowerOfTwo(result.longValue(), num) : result.longValue() * num);
                break;
            case "/":
                result = new BigDecimal(result.longValue() / num);
                break;
        }
    }

    /**
     * 基本运算
     *
     * @param calculator
     * @param operation
     * @param numStr
     */
    private void performOperation(Calculator calculator, String operation, String numStr) {
        BigDecimal oldResult = result;
        BigDecimal num = parseNumber(numStr);

        //判断是否含有小数运算
        if (hasDecimal(result) || isDecimal(numStr)) {
            performDecimalOperation(operation, num);
        } else {
            long inputNum = Long.parseLong(numStr);
            performIntegerOperation(operation, inputNum);
        }
        history.push(new OperationRecord(operation, oldResult, result, num));
        redoHistory.clear();
        System.out.println("Current Result: " + calculator.getResult().toString());
    }


    /**
     * 撤销最近的一次操作。
     */
    public void undo() {
        if (!history.isEmpty()) {
            OperationRecord last = history.pop();
            result = last.before;
            redoHistory.push(last);
        } else {
            System.out.println("No more operations to undo.");
        }
    }

    /**
     * 重做最近的一次撤销操作。
     */
    public void redo() {
        if (!redoHistory.isEmpty()) {
            OperationRecord last = redoHistory.pop();
            history.push(last);
            result = last.after;
        } else {
            System.out.println("No operations to redo.");
        }
    }

    /**
     * 清空上下文，将计算器恢复到初始状态。
     */
    public void clear() {
        result = BigDecimal.ZERO;
        history.clear();
        redoHistory.clear();
        System.out.println("Calculator clear!");
    }


    /**
     * 获取当前计算结果。
     *
     * @return 当前计算结果
     */
    public BigDecimal getResult() {
        return result;
    }

    /**
     * 解析输入的字符串为 BigDecimal。
     *
     * @param numStr 输入的字符串
     * @return 解析后的 BigDecimal
     */
    private BigDecimal parseNumber(String numStr) {
        try {
            return new BigDecimal(numStr);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid number format: " + numStr);
        }
    }

    /**
     * 校验操作数是否合法
     *
     * @param calculator
     * @param operation
     * @param operationNum
     * @return
     */
    private static boolean checkOperationNum(Calculator calculator, String operation, String operationNum) {
        boolean result = false;
        switch (operation) {
            case "/":
                //防止除数为0的异常情况
                if ("0".equals(operationNum)) {
                    System.out.println("Cannot divide by zero.");
                    result = true;
                    System.out.println("Current Result: " + calculator.getResult().toString());
                }
        }
        return result;
    }

    /**
     * 执行特殊操作
     *
     * @param calculator
     * @param operation
     */
    private static void executeSpecialOperation(Calculator calculator, String operation) {
        switch (operation) {
            case "undo":
                calculator.undo();
                break;
            case "redo":
                calculator.redo();
                break;
            case "clear":
                calculator.clear();
                return;
            default:
                System.out.println("Invalid operation. Please enter a valid operation.");
                break;
        }
        System.out.println("Current Result: " + calculator.getResult().toString());
    }

    /**
     * 输入操作
     *
     * @param scanner
     * @param isInit
     * @return
     */
    private static String inputOperation(Scanner scanner, boolean isInit) {
        while (true) {
            //若是仅有初始值
            if (isInit || history.isEmpty()) {
                System.out.print("Enter operation (+/-/*///clear/quit): ");
            } else {
                System.out.print("Enter operation (+/-/*///undo/redo/clear/quit): ");
            }
            String operation = scanner.nextLine().trim();
            if (operationSet.contains(operation)) {
                return operation;
            }
            System.out.println("Invalid operation. Please enter a valid operation.");
        }
    }


    /**
     * 输入数字
     *
     * @param scanner
     * @param isfirst 是否为初始数据
     */
    private static String inputNum(Scanner scanner, boolean isfirst) {
        while (true) {
            //输入第一个数字
            System.out.print("Enter a number: ");
            String numStr = scanner.nextLine().trim();

            //验证输入是否为数字
            if (isNumeric(numStr)) {
                if (isfirst) {
                    result = new BigDecimal(numStr);
                }
                return numStr;
            } else {
                System.out.print("Number illegal, please enter a valid number.");
            }
        }
    }

    /**
     * 判断一个整数是否是 2 的幂。
     *
     * @param num 输入的整数
     * @return 如果是 2 的幂返回 true，否则返回 false
     */
    public static boolean isPowerOfTwo(long num) {
        if (num <= 0) {
            return false;
        }
        return (num & (num - 1)) == 0;
    }

    /**
     * 使用位运算实现乘以 2 的幂。
     *
     * @param multiplier 乘数
     * @param power      幂次
     * @return 乘法的结果
     */
    public static long multiplyByPowerOfTwo(long multiplier, int power) {
        return multiplier << power;
    }

    /**
     * 判断输入数是否为 2 的幂，并使用位运算实现乘法。
     *
     * @param num       原来的整数
     * @param multiplier 乘数
     * @return 如果 num 是 2 的幂，返回乘法的结果；否则返回 -1
     */
    public static long multiplyIfPowerOfTwo(long num, long multiplier) {
        if (!isPowerOfTwo(multiplier)) {
            throw new IllegalArgumentException("Power must be a power of two.");
        }
        int power = Integer.numberOfTrailingZeros((int) multiplier);
        return multiplyByPowerOfTwo(num, power);
    }

    /**
     * 判断字符串是否为数字。
     *
     * @param strNum 字符串
     * @return 是否为数字
     */
    public static boolean isNumeric(String strNum) {
        if (strNum == null) {
            return false;
        }
        //正则表达式匹配数字
        String regex = "^[-+]?[0-9]+(\\.[0-9]+)?$";
        return strNum.matches(regex);
    }

    /**
     * 判断字符串是否为数字，并且包含小数部分。
     *
     * @param str 输入的字符串
     * @return 如果是数字并且包含小数部分，返回true；否则返回false
     */
    public static boolean isDecimal(String str) {
        //正则表达式匹配数字，并且包含小数部分
        String regex = "^[-+]?[0-9]*\\.\\d+$";
        return str.matches(regex);
    }

    /**
     * 判断 BigDecimal 是否包含小数部分。
     *
     * @param number 输入的 BigDecimal 数值
     * @return 如果包含小数部分，返回 true；否则返回 false
     */
    public static boolean hasDecimal(BigDecimal number) {
        //使用 scale() 方法判断是否有小数部分
        int scale = number.stripTrailingZeros().scale();
        return scale > 0;
    }


    /**
     * 启动终端交互
     *
     * @param args
     */
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        Calculator calculator = new Calculator();

        System.out.println("Welcome to the Calculator!");
        boolean isInit;
        while (true) {
            isInit = result == BigDecimal.ZERO;
            //输入初始值(含校验)
            if (isInit) {
                inputNum(scanner, true);
            }
            //输入操作(含校验)
            String operation = inputOperation(scanner, isInit);
            //退出
            if (quit.equalsIgnoreCase(operation)) {
                break;
            }
            //执行特殊操作(如撤销、重做和清空)
            if (specialOperationSet.contains(operation)) {
                executeSpecialOperation(calculator, operation);
            } else {
                //输入操作数(含校验)
                String operationNum = inputNum(scanner, false);
                //校验输入操作数的合法性（例如除法中除数不为0）
                boolean operationNumIllegal = checkOperationNum(calculator, operation, operationNum);
                //非法
                if (operationNumIllegal) continue;
                //数字运算
                calculator.performOperation(calculator,operation, operationNum);
            }
        }
        scanner.close();
        System.out.println("Quit Calculator!");
    }


}
